
// Copyright (C) 2009-2011 Luca Piccioni
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//   
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;

using Derm.OpenGL;
using Derm.Raster;

namespace Derm.Render
{
	/// <summary>
	/// Cube texture.
	/// </summary>
	public class TextureCube : Texture
	{
		#region Constructors
		
		#endregion

		#region Texture Data Storage

		/// <summary>
		/// Enuemartion defining cube texture faces.
		/// </summary>
		public enum CubeFace
		{
			/// <summary>
			/// Positive X-axis face.
			/// </summary>
			XPositive = 0,
			/// <summary>
			/// Negative X-axis face.
			/// </summary>s
			XNegative,
			/// <summary>
			/// Positive Y-axis face.
			/// </summary>
			YPositive,
			/// <summary>
			/// Negative Y-axis face.
			/// </summary>
			YNegative,
			/// <summary>
			/// Positive Z-axis face.
			/// </summary>
			ZPositive,
			/// <summary>
			/// Negative Z-axis face.
			/// </summary>
			ZNegative
		}

		/// <summary>
		/// Texture data.
		/// </summary>
		private List<TextureData>[] mTextureData = new List<TextureData>[6] {
			new List<TextureData>(),
			new List<TextureData>(),
			new List<TextureData>(),
			new List<TextureData>(),
			new List<TextureData>(),
			new List<TextureData>()
		};

		#endregion

		#region Texture Overrides

		/// <summary>
		/// Release texture data.
		/// </summary>
		/// <remarks>
		/// <para>
		/// If the current OpenGL implementation  supports texture objects, it is possible to release host texture data,
		/// since it is managed by OpenGL. To avoid unnecessary data duplication, TextureCube implementation release
		/// the textel data for each cube face, including all defined mipmaps.
		/// </para>
		/// </remarks>
		protected override void ReleaseData()
		{
			foreach (List<TextureData> dataList in mTextureData)
				ReleaseData(dataList);
		}

		/// <summary>
		/// Texture width.
		/// </summary>
		public override uint Width { get { return (mSize); } }

		/// <summary>
		/// Texture height.
		/// </summary>
		public override uint Height { get { return (mSize); } }

		/// <summary>
		/// Texture depth.
		/// </summary>
		/// <remarks>
		/// Only Texture3d target has a depth. For every else texture target, it is set to 1.
		/// </remarks>
		public override uint Depth { get { return (1); } }

		/// <summary>
		/// Texture size (cube map textures have equal width and height).
		/// </summary>
		public uint Size { get { return (mSize); } }

		/// <summary>
		/// Determine the derived Texture target.
		/// </summary>
		/// <remarks>
		/// In the case a this Texture is defined by multiple targets (i.e. cube map textures), this property
		/// shall returns always 0.
		/// </remarks>
		public override int TextureTarget { get { return (Gl.TEXTURE_CUBE_MAP); } }

		/// <summary>
		/// Uniform sampler type for managing this Texture.
		/// </summary>
		internal override int SamplerType { get { return (Gl.SAMPLER_CUBE); } }

		/// <summary>
		/// Texture size (width and height are the same).
		/// </summary>
		private uint mSize = 0;

		#endregion

		#region RenderResource Overrides

		/// <summary>
		/// Create this RenderResource.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for creating this object.
		/// </param>
		/// <exception cref="ArgumentException">
		/// Exception throw if <see cref="Width"/> or <see cref="Height"/> equals to 0.
		/// </exception>
		/// <exception cref="InvalidOperationException">
		/// Exception throw if <see cref="Texture.PixelFormat"/> equals to <see cref="Derm.Pixel.Type.None"/>.
		/// </exception>
		public override void Create(RenderContext ctx)
		{
			if (Size == 0)
				throw new InvalidOperationException("zero size");
			if (PixelFormat == Pixel.Type.None)
				throw new InvalidOperationException("invalid pixel format");

			// Note: this override is necessary to check invalid use of Create(RenderContext) method. Essentially
			// user code should not call directly this method, since he's not able to set minimum required attributes
			// to define a texture without a Create method call (except this one).

			base.Create(ctx);
		}

		/// <summary>
		/// Actually create this Texture2d resources.
		/// </summary>
		/// <param name="ctx">
		/// A <see cref="RenderContext"/> used for allocating resources.
		/// </param>
		protected override void CreateObject(RenderContext ctx)
		{
			// Bind this Texture
			Bind(ctx);
			// Upload texture data
			int internalFormat = Pixel.GetGlInternalFormat(PixelFormat, ctx);

			if (mTextureData != null) {
				for (int i = 0; i < mTextureData.Length; i++) {
					foreach (TextureData data in mTextureData[i]) {
						int type = Pixel.GetGlDataFormat(data.TextelFormat);
						int format = Pixel.GetGlFormat(data.TextelFormat);

						// Set pixel transfer
						foreach (int alignment in new int[] { 8, 4, 2, 1 }) {
							if (data.Stride % alignment == 0) {
								Gl.PixelStore(Gl.UNPACK_ALIGNMENT, alignment);
								RenderException.DebugCheckErrors();
								break;
							}
						}

						// Upload texture contents
						Gl.TexImage2D(mCubeTargets[i], data.MipmapLevel, internalFormat, (int)Size, (int)Size, 0, format, type, data.Data);
						RenderException.CheckErrors();
					}
				}
			} else {
				// Define texture contents
				for (int i = 0; i < 6; i++) {
					Gl.TexImage2D(mCubeTargets[i], 0, internalFormat, (int)Width, (int)Height, 0, /* Unused */ Gl.RGB, /* Unused */ Gl.UNSIGNED_BYTE, null);
					RenderException.CheckErrors();
				}
			}
			// Unbind this texture
			Unbind(ctx);
		}

		/// <summary>
		/// TextureCube target ordering.
		/// </summary>
		/// <remarks>
		/// It must following <see cref="CubeFace"/>.
		/// </remarks>
		private readonly int[] mCubeTargets = new int[] {
			Gl.TEXTURE_CUBE_MAP_POSITIVE_X,
			Gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
			Gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
			Gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
			Gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
			Gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
		};

		#endregion
	}
}
